package net.flyinggroup.gateway.exception;

import net.flyinggroup.util.exception.FlyingBusinessException;
import net.flyinggroup.util.exception.FlyingRuntimeException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import org.zalando.problem.Problem;
import org.zalando.problem.Status;
import org.zalando.problem.StatusType;
import org.zalando.problem.spring.common.HttpStatusAdapter;
import org.zalando.problem.spring.webflux.advice.ProblemHandling;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.Optional;

/**
 * @author Walker Zhang
 */
@ControllerAdvice
public class ExceptionHandling implements ProblemHandling {
    @Value("${flying.api-exceptions.path:/exceptions}")
    private String exceptionPath;

    private URI buildType(ServerHttpRequest request, String... paths) {
        URI requestUri = request.getURI();
        return UriComponentsBuilder
                .newInstance()
                .scheme(requestUri.getScheme())
                .host(requestUri.getHost())
                .port(requestUri.getPort())
                .path(exceptionPath + "/{type}")
                .build(String.join(".", paths));
    }

    @ExceptionHandler
    public Mono<ResponseEntity<Problem>> handleThrowable(final FlyingBusinessException flyingBusinessException,
                                                         final ServerWebExchange exchange) {
        StatusType status = Optional.ofNullable(resolveResponseStatus(flyingBusinessException))
                .<StatusType>map(responseStatus -> new HttpStatusAdapter(responseStatus.code()))
                .orElse(Status.BAD_REQUEST);
        return create(status, flyingBusinessException, exchange, buildType(exchange.getRequest(),
                flyingBusinessException.getClass().getName(),
                flyingBusinessException.getType(),
                flyingBusinessException.getDefinition()));
    }

    public Mono<ResponseEntity<Problem>> handleThrowable(final FlyingRuntimeException flyingRuntimeException,
                                                         final ServerWebExchange exchange) {
        StatusType status = Optional.ofNullable(resolveResponseStatus(flyingRuntimeException))
                .<StatusType>map(responseStatus -> new HttpStatusAdapter(responseStatus.code()))
                .orElse(Status.BAD_REQUEST);
        return create(status, flyingRuntimeException, exchange, buildType(exchange.getRequest(),
                flyingRuntimeException.getClass().getName(),
                flyingRuntimeException.getType()));
    }

    @ExceptionHandler
    public Mono<ResponseEntity<Problem>> handleThrowable(final Throwable throwable,
                                                         final ServerWebExchange exchange) {
        StatusType status = Optional.ofNullable(resolveResponseStatus(throwable))
                .<StatusType>map(responseStatus -> new HttpStatusAdapter(responseStatus.code()))
                .orElse(Status.INTERNAL_SERVER_ERROR);
        return create(status, throwable, exchange, buildType(exchange.getRequest(), throwable.getClass().getName()));
    }
}